Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 22 Grafische Programmierung mit GDI+
  gp 22.1 Die Namespaces der GDI+-Schnittstelle
  gp 22.2 Die Klasse »Graphics«
    gp 22.2.1 Überblick über die Klasse »Graphics«
    gp 22.2.2 Sich die Referenz auf das »Graphics«-Objekt besorgen
    gp 22.2.3 Das Neuzeichnen einer Grafik mit »ResizeRedraw« und »Invalidate«
    gp 22.2.4 Zerstören von grafischen Objekten (Dispose)
    gp 22.2.5 Das Koordinatensystem von GDI+
    gp 22.2.6 Festlegen eines anderen Ursprungspunkts
    gp 22.2.7 Die grafischen Methoden der Klasse »Graphics«
    gp 22.2.8 Eine Linie zeichnen
    gp 22.2.9 Mehrere Linien zeichnen
    gp 22.2.10 Rechtecke zeichnen
    gp 22.2.11 Punkte zeichnen
    gp 22.2.12 Polygone zeichnen
    gp 22.2.13 Ellipsen, Ellipsenbogen und Ellipsensegment
    gp 22.2.14 Kurvenzüge
    gp 22.2.15 Bézierkurven
  gp 22.3 Elementare Klassen für grafische Operationen
    gp 22.3.1 Die Klasse »Brush«
    gp 22.3.2 Die Klasse »Pen«
    gp 22.3.3 Farbeinstellungen mit »Color«
  gp 22.4 Die Schriftdarstellung
    gp 22.4.1 Allgemeines
    gp 22.4.2 Die Klassen »Font« und »FontFamily«
    gp 22.4.3 Der Schriftstil mit »FontStyle«
    gp 22.4.4 Die grafische Ausgabe einer Zeichenfolge
    gp 22.4.5 Die Abmessungen mit »MeasureString« ermitteln
    gp 22.4.6 Die Klasse »StringFormat«
  gp 22.5 Bilddateien
    gp 22.5.1 Bilder und Grafiken der .NET-Klassenbibliothek
    gp 22.5.2 Die Bitmap-Dateiformate
    gp 22.5.3 Bilder mit der Klasse »Image«
    gp 22.5.4 Bitmaps


Galileo Computing

22.5 Bilddateiedowntop

Grafiken, Bilder und Zeichnungen werden mit Hilfe verschiedener Technologien dargestellt, bearbeitet und letztendlich auch gespeichert. Prinzipiell werden dabei zwei Kategorien zur Bearbeitung grafischer Elemente unterschieden:

gp  Rastergrafiken
gp  Vektorgrafiken

Rastergrafiken

Meistens werden Bilder oder Fotos als Rastergrafik im Bitmap-Format hinterlegt. Ein Bild ist dann eine Aneinanderreihung einzelner Bildpunkte, die durch eine Farbe beschrieben werden. Weil eine Rastergrafik sehr simpel aufgebaut wird, ist auch ihre Erzeugung bzw. Nachbearbeitung sehr einfach. Allerdings erkauft man sich diesen Vorteil zu einem hohen Preis, denn Rasterbilder haben einige eklatante Nachteile.

gp  Es können keine »glatten« Kurven dargestellt werden, weil sich die Kurve aus vielen einzelnen Rechtecken (Quadraten) zusammensetzt.
gp  Eine Rastergrafik benötigt sehr viel Speicherplatz. Diesem Nachteil begegnet man mit zahlreichen Komprimierungsverfahren, um die Datenmenge einer Rastergrafik auf ein Minimum zu beschränken. GIF- und JPEG-Dateien, die auch Rastergrafiken nach einem bestimmten Kodierungsverfahren komprimieren, sind nur die bekanntesten Beispiele.
gp  Bei einer Skalierung (Größenänderung) einer Rastergrafik kommt es zu sehr unschönen optischen Effekten. Beispielsweise werden bei einer Verkleinerung einfach Spalten oder Zeilen weggelassen.

Vektorgrafiken

Bei einer Vektorgrafik werden Linien, Kurven und Texte durch Vektoren beschrieben. Einen Vektor können Sie sich als Linie vorstellen, die durch einen Anfangspunkt, eine Richtung und eine Länge beschrieben ist. Die Aneinanderreihung vieler genügend kleiner Vektoren beschreibt ein Bildelement.

Jeder Vektor wird durch eine Funktion beschrieben. Daraus ergeben sich einige Vorteile.

gp  Eine Vektorgrafik kann sehr kompakt gespeichert werden. Stellen Sie sich dazu nur eine Diagonale in einem Rechteck mit den Abmessungen 300 x 500 Einheiten vor. Als Rastergrafik müssen 150.000 Bildpunkte gespeichert werden, als Vektorgrafik genügt die Beschreibung eines Vektors mit Angabe des Anfangs- und Endpunktes ((0,0),(300,500)).
gp  Da die Bildelemente durch Funktionen beschrieben werden, kann eine Vektorgrafik beliebig skaliert werden. Dazu rechnet das Programm die Koordinaten um und zeichnet die Elemente neu.
gp  In einigen Programmen kann das Bearbeiten einer Vektorgrafik einfacher sein als das einer Rastergrafik (beispielsweise CAD-Programme), denn das Programm kennt die einzelnen Elemente und hat direkten Zugriff darauf. Mit der Maus lassen sich die einzelnen Bildelemente sehr einfach greifen und bearbeiten.

Das hört sich ausgesprochen vorteilhaft an und stellt die Frage in den Raum, warum Vektorgrafiken die Rastergrafiken bisher nicht ablösen konnten. Der Grund ist ein Nachteil eines Vektors: Eine komplexe Bitmap, z.B. ein Foto, kann die Vorteile einer Vektorgrafik nicht ausspielen, denn dazu müsste jeder Bildpunkt durch einen Vektor beschrieben werden.

Zusammenfassend kann man die folgenden Feststellungen treffen.

gp  Zeichenprogramme, die hauptsächlich auf Linien und Kurven basieren, speichern die Daten des Bildes als Vektordatei, die binäre Daten enthält.
gp  Malprogramme und Bilder werden im Bitmap-Format gespeichert.

Galileo Computing

22.5.1 Bilder und Grafiken der .NET-Klassenbibliothek  downtop

Rastergrafiken werden als Bitmaps gespeichert, Vektorgrafiken als Metadateien. Die Klassenbibliothek stellt mit den Klassen Image, Bitmap, Icon und Metafile Typen bereit, die in der Klassenhierarchie wie in der folgenden Abbildung gezeigt miteinander in Beziehung stehen.

Die Klasse Image dient als abstrakte Basisklasse für die beiden Typen Bitmap und Metafile und stellt diesen Funktionen zur Bildbearbeitung zur Verfügung. Ein wenig aus dem Rahmen fällt die Klasse Icon. Grundsätzlich ist auch ein Icon-Objekt eine Bitmap, wird aber in einem Windows-eigenen Dateiformat gespeichert und weist nur wenig Bearbeitungsmethoden auf.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 22.28   Hierarchie der bildbeschreibenden .NET-Klassen

Der Bitmap-Klasse kommt eine deutlich höhere Bedeutung zu als den Klassen Icon und Metafile. Im Weiteren werden wir uns daher mit den Möglichkeiten einer Bitmaps beschäftigen.


Galileo Computing

22.5.2 Die Bitmap-Dateiformate  downtop

Eine Bitmap ist die Ansammlung von Pixeln in einem Array, Höhe und Breite werden in Pixeln angegeben. Jedes Pixel eines Bitmaps hat eine Farbtiefe, welche die Anzahl der darstellbaren Farben beschreibt. Die Farbtiefe wird von der Anzahl der Bits bestimmt, die jedem Pixel eines Bitmaps gleichermaßen zur Verfügung steht. Die Anzahl der Bits liegt dabei zwischen 1 (zumindest theoretisch) und 32 Bits, die Anzahl der darstellbaren Farben folgt der Gleichung

Farbenanzahl = 2Bits pro Pixel 

Für die RGB-Farbtiefe wird für den Rot-, Grün- und Blauanteil (die auch als Primärfarben bezeichnet werden) jeweils ein Byte bereitgestellt, das insgesamt 256 Farbabstufungen beschreiben kann. Die ARGB-Farbskala stellt noch ein viertes Byte bereit, mit dem die Transparenz ebenfalls zwischen 0 (durchsichtig) und 255 (undurchsichtig) eingestellt werden kann.

Es haben sich in der Vergangenheit einige feste Bitmap-Formate etabliert. Werden die Farben eines Pixels durch acht Bit beschrieben, ergeben sich 256 Farbabstufungen. Meistens wird dieses Format zur Darstellung von Schwarz-Weiß-Bitmaps benutzt. Die 256 Farben liefern dann die verschiedenen Graustufen. Stehen jedem Pixel drei Byte mit je acht Bit zur Farbdarstellung zur Verfügung, erhöht sich die Farbanzahl sofort dramatisch auf 224  = 16.777.216. Wir kennen diese Farbtiefe unter dem Namen True Color.

Der Nachteil herkömmlicher Bitmaps ist die Größe der Datei. Eine Bitmap mit den Abmessungen 300 x 300 Pixel und einer Farbtiefe von 24 Bit beansprucht für sich alleine bereits etwas über 260 KByte. In der Vergangenheit, als die Rechner noch nicht so üppig mit Hauptspeicher ausgerüstet waren wie heutzutage, waren das schon Größenordnungen, die kaum noch akzeptabel waren. Heutzutage mangelt es den Maschinen zwar nicht mehr an RAM, allerdings gibt es ein anderes Nadelöhr hinsichtlich des Bytevolumens: Es ist das Web. Eine Internetseite, die mehrere Bitmaps beinhaltet, kann ein unzumutbares Ladeverhalten zeigen.

Die verschiedenen Dateiformate, unter denen Bitmaps abgespeichert werden können, finden Sie in der folgenden Tabelle.


Tabelle 22.9   Dateiformate für Bitmaps

Bitmap-Dateiformat Beschreibung
BMP Eine BMP-Datei ist das systemeigene Standardformat von Windows für Bitmap-Dateien. Die Farbtiefe ist auf 24 Bit beschränkt und wird unkomprimiert gespeichert.
GIF Das Graphics Interchange Format speichert maximal 256 Farben, verfügt jedoch über ein hochwertiges Komprimierungsverfahren und eignet sich für transparente Bildbereiche sowie für die im Internet häufig anzutreffenden Animationssequenzen.
JPEG Bei dem JPEG-Format (Joint Photographic Experts Group) handelt es sich um ein international anerkanntes Komprimierungsverfahren, das für die Bildbearbeitung entwickelt worden ist. Das Format ist auch als JFIF (JPEG File Interchange Format) bekannt geworden.
PNG Hierbei handelt es sich um einen noch relativ jungen Bildstandard, der für das Internet entwickelt wurde und von den meisten Browsern neben GIF und JPEG unterstützt wird. PNG (Portable Network Graphic) bietet hohe Komprimierungsraten, Halbtransparenz und Echtfarben.
TIFF Das TIFF-Format (Tagged Image File Format) dient zum Austauschen von Dateien zwischen unterschiedlichen Programmen und Plattformen. TIFF ist ein flexibles Bitmap-Format, das von nahezu allen Mal- und Bildbearbeitungsprogrammen unterstützt wird.


Galileo Computing

22.5.3 Bilder mit der Klasse »Image«  downtop

Ein Image laden

Image ist die abstrakte Basisklasse der beiden abgeleiteten Klassen Bitmap und Metafile. Das bedeutet folglich, dass es keinen Konstruktor in der Klasse Image gibt, um daraus ein Objekt zu erzeugen.

Über mehrere statische Methoden von Image können Sie sich die Referenz eines Bildes besorgen. Die am häufigsten benutzte ist die, die ein Image aus einer Datei erstellt, deren Zugriffspfad als String-Argument übergeben wird:


public static Image FromFile(string);

Wir wollen diese Methode im folgenden Beispielprogramm benutzen und eine JPEG-Datei in einer Form anzeigen. Sie finden das Bild auf der Buch-CD unter

...\Bilder\Egypt.jpg

Kommen wir zum Programmcode.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 22\ImageAnzeigen
// --------------------------------------------------------------
private void Form1_Paint(object sender, PaintEventArgs e) {
  try {
    string strFile = "..\\..\\..\\..\\Bilder\\Egypt.jpg";
    Image img = Image.FromFile(strFile);
    e.Graphics.DrawImage(img, 0 ,0);
  }
  catch {
    // Anweisungen
  }
}

Der Programmcode ist im Ereignishandler des Paint-Ereignisses implementiert, denn um das Bild im Arbeitsbereich der Form auszugeben, greifen wir auf eine Methode des Graphics-Objekts zu, die wir bisher noch nicht behandelt haben: DrawImage gibt ein Image-Objekt an einer bestimmten Position aus. Hierbei handelt es sich um den linken oberen Koordinatenpunkt des Bereichs, in den gezeichnet werden soll. Im Beispiel ist das der Ursprung (0, 0). Die Referenz auf das Image besorgen wir uns vorher mit der statischen FromFile-Methode der Klasse Image, der wir die Zeichenfolge, die den Pfad auf die Bilddatei beschreibt, übergeben. Wird die Datei nicht gefunden, wird eine Ausnahme ausgelöst, die behandelt werden muss.


Hinweis   In diesem Beispiel ist der Zugriff auf das Bild aus Demonstrationszwecken hardcodiert. In der Praxis sollten Sie das jedoch grundsätzlich vermeiden.

Die Bildanzeige mit der »DrawImage«-Methode

Haben Sie sich die Definition der Methode DrawImage in der .NET-Dokumentation angesehen? Wenn nicht, sollten Sie das nachholen, denn die insgesamt 30 Überladungen bieten eine Vielzahl von Möglichkeiten zur Bildausgabe: Bilder können gestreckt, gestaucht oder unter einem Drehwinkel angezeigt werden.

Zwei weitere Beispiele sollen die Flexibilität der Zeichenmethode DrawImage demonstrieren. Sie brauchen dazu nur die Zeile des Methodenaufrufs von DrawImage im Beispiel oben gegen die folgende Anweisung auszutauschen:


e.Graphics.DrawImage(img, new Rectangle(0, 0, 250, 150));

Dem Methodenaufruf liegt die überladene Version zugrunde, die ein Rectangle-Objekt erwartet, das die Lage und die Abmessungen des Bereichs beschreibt, in dem das Bild angezeigt werden soll. Die daraus resultierende Bildausgabe sehen Sie in der folgenden Abbildung – das Bild wird in seiner Breite gestreckt.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 22.29   Ausgabe eines gestreckten Bildes

Tauschen wir noch einmal den DrawImage-Aufruf gegen einen anderen aus und übergeben ein Point-Array. Dieser Übergabe liegt die folgende überladene Methode zugrunde:


public void DrawImage(Image image, Point[] destPoints);

In diesem Point-Array sind drei Punkte definiert: Der erste gibt den Lagepunkt der oberen linken Ecke des Bildes an, der zweite den Lagepunkt der oberen rechten und der dritte den der unteren rechten Ecke. Der vierte Punkt wird automatisch so ermittelt, dass das Ergebnis ein Parallelogramm bildet:


e.Graphics.DrawImage(img,new Point[]{new Point(80,0),
                                     new Point(300,80),
                                     new Point(30,100)});

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 22.30   Image, gedreht und gestreckt angezeigt

Das Zeichnen auf Bildern

Das Zeichnen auf einem Image ist nicht weiter schwierig. Wir rufen dazu sowohl die Methode DrawImage als auch die Methode DrawString auf, der wir nur die Zeichenfolge samt der Schriftart und -farbe und natürlich auch die Koordinaten, an denen die Schrift ausgegeben werden soll, übergeben.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 22\AufImageZeichnen
// --------------------------------------------------------------
private void Form1_Paint(object sender, PaintEventArgs e) {
   string strFile = "..\\..\\..\\..\\Bilder\\Egypt.jpg";
   Image img = Image.FromFile(strFile);
   string strText = "Ägyptischer Restaurateur";
   Font font = new Font("Arial", 12, FontStyle.Bold | FontStyle.Underline);
   e.Graphics.DrawImage(img, 0, 0);
   e.Graphics.DrawString(strText, font, Brushes.Yellow, new Point(5,5));
}

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 22.31   Bildausgabe mit Beschriftung

Eigenschaften der Klasse »Image«

Als abstrakte Basisklasse stellt Image Eigenschaften bereit, die von den beiden abgeleiteten Klassen Metafile und Bitmap geerbt werden. Diese Eigenschaften stellen uns Informationen über das Image-Objekt zur Verfügung. In der folgenden Tabelle sind einige wichtige Eigenschaften wiedergegeben.


Tabelle 22.10   Eigenschaften eines »Image«-Objekts (Auswahl)

Eigenschaft Beschreibung
Height Liefert die Höhe des Bildes in Pixel.
HorizontalResolution Liefert die horizontale Auflösung des Bildes in DPI (Dots per Inch).
PhysicalDimension Liefert ein SizeF-Objekt zurück, das die Größe des Bildes beschreibt. Die Dokumentation gibt keine Auskunft über die Einheiten, vermutlich handelt es sich jedoch um 1/100 mm.
PixelFormat Ruft das Pixelformat des Bildes ab.
RawFormat Ruft das Bildformat des Images ab. Der Rückgabewert ist vom Typ ImageFormat (siehe Tabelle 22.11).
VerticalResolution Liefert die vertikale Auflösung des Bildes in DPI (Dots per Inch).
Width Die Breite des Bildes in Pixel.


Galileo Computing

22.5.4 Bitmaps  toptop

Eine neue Bitmap erzeugen

Die Klasse Image ist, wie wir gesehen haben, nicht instanziierbar. Ganz anders die Klasse Bitmap, die aus Image abgeleitet ist. Dem überladenen Konstruktor können Sie dabei die Referenz auf ein Image-Objekt, einen Stream oder eine Zeichenfolge übergeben, die den Pfad zu einer Bilddatei beschreibt. Am einfachsten lässt sich eine Bitmap aber mit dem Konstruktor erzeugen, der zwei Integer erwartet, mit denen die Breite und die Höhe der Bitmap in Pixel beschrieben werden:

Bitmap bmp = new Bitmap(300, 400);

Die erzeugte Bitmap ist 300 Pixel breit und 400 Pixel hoch. Es handelt sich um eine ARGB-Bitmap (A = Alpha-Kanal, R = Rot, G = Grün, B = Blau), deren Werte mit 0 initialisiert werden. Da der A-Kanal zur Darstellung der Transparenz den Startwert 0 hat, bedeutet das, dass eine Bitmap (zunächst) durchsichtig ist.

Die Methode »SetPixel«

Mit der Methode SetPixel können Sie den Zustand eines einzelnen Pixels in der Bitmap ändern:


bmp.SetPixel(10, 10, Color.Blue);

Die ersten beiden Parameter geben die Koordinaten eines Pixels an, der mit der im dritten Parameter genannten Farbe gezeichnet wird. Mit der DrawImage-Methode des Graphics-Objekts können Sie sich die Bitmap auf einer grafikfähigen Oberfläche anzeigen lassen. Dabei darf es sich sogar um eine Schaltfläche handeln, die auch eine Paint-Methode bereitstellt. Der Beispielcode unten benutzt die wohl einfachste Form der DrawImage-Zeichenmethode, aber es kann natürlich auch jede beliebige Überladung aufgerufen werden, um sehr ansehnliche optische Effekte zu erzielen.


private void button1_Paint(object sender, PaintEventArgs e) {
  Bitmap bmp = new Bitmap(255, 255);
  for (int i = 0; i < 255; i++)
    for (int j = 0; j < 255; j++)
      bmp.SetPixel(j, i, Color.FromArgb(i, 0, j));
  e.Graphics.DrawImage(bmp, 0, 0);
}

Die Bitmap, die im Codefragment erzeugt wird, hat eine Höhe und Breite von jeweils 255 Pixeln. In zwei for-Schleifen wird für jedes Pixel in der Bitmap eine neue Farbe festgelegt. Die äußere Schleife durchläuft dabei jede Pixelzeile, die innere jede Pixelspalte. Die Farbe des Rot- und Blauanteils wird aus dem jeweiligen Zählerstand mit der statischen FromArgb-Methode der Color-Klasse ermittelt.

Benötigen Sie die Farbe eines Pixels, hilft das Pendant der SetPixel-Methode weiter: GetPixel. Sie müssen wieder die Koordinaten des entsprechenden Pixels angeben und erhalten als Rückgabewert die Color-Struktur, die der Farbe entspricht:

Speichern von Bitmaps

Wie ein Bild aus einer Datei geladen werden kann, haben Sie in den vergangenen Abschnitten gesehen. Ebenso einfach ist es, ein Image in einer Datei zu speichern. Die Klasse Image bietet dazu die überladene Methode Save an, die von der abgeleiteten Klasse Bitmap geerbt wird. Von dieser Methode gibt es eine einparametrige Version, die nur die Angabe des Speicherpfades als Zeichenfolge entgegennimmt. Diese einzusetzen empfiehlt sich allerdings nicht, da das Bildformat keinem bekannten Bildformat entspricht und sich die Datei sich aus dem Windows Explorer heraus nicht öffnen lässt.

Sie sollten beim Speichern grundsätzlich immer das Bildformat angeben, und dazu ist die folgende Überladung der Save-Methode geeignet:


public void Save(string, ImageFormat);

Anstelle des String-Parameters können Sie die Bitmap auch an ein Stream-Objekt übergeben.

Der Parameter vom Typ ImageFormat basiert auf der nicht ableitbaren Klasse System.Drawing.Imaging.ImageFormat, die über insgesamt elf statische Eigenschaften das Bildformat abruft. Von diesen elf Eigenschaften sind für uns nur zehn von Interesse, die der nachfolgenden Tabelle entnommen werden können.


Tabelle 22.11   Die statischen Eigenschaften der Klasse »ImageFormat«

Eigenschaft Beschreibung
Bmp Ruft das Bitmap-Bildformat (BMP) ab.
Emf Ruft das Windows-Bildformat Erweiterte Metadatei (Enhanced Meta File – EMF) ab.
Exif Ruft das Exif-Format (Exchangeable Image File) ab.
Gif Ruft das GIF-Bildformat (Graphics Interchange Format) ab.
Icon Ruft das Bildformat für Windows-Symbole ab.
Jpeg Ruft das JPEG-Format (Joint Photographic Experts Group) ab.
MemoryBmp Ruft ein Bitmap-Bildformat im Speicher ab.
Png Ruft das PNG-Bildformat (W3C Portable Network Graphics) ab.
Tiff Ruft das TIFF-Bildformat (Tagged Image File Format) ab.
Wmf Ruft das WMF-Bildformat (Windows Metafile) ab.

Zum Speichern geben wir jetzt nur den Pfad und den Dateinamen an. Beachten Sie dabei, dass die Dateierweiterung mit der Angabe des Bildformats übereinstimmt:


bmp.Save(@"C:\MyBitmap.jpg", ImageFormat.Jpeg);

Je nachdem, welches Format Sie angeben, wird das zu speichernde Bild mit dem entsprechenden Verfahren komprimiert. Speichern Sie die Bitmap beispielsweise als GIF-Datei, verringert sich die Anzahl der Farben auf 256, was zu einem Verlust der Darstellungsqualität führt.

Zwischenspeichern einer Bitmaps

Im folgenden Beispielprogramm kann mit der Maus eine Grafik auf die Clientfläche der Form gezeichnet werden. Dazu wird die linke Maustaste gedrückt, und mit gedrückter Maustaste wird die »Zeichnung« erstellt.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 22\Einfaches Malprogramm
// --------------------------------------------------------------
public partial class Form1 : Form  {
  private bool mouseFlag;
  private Point lastPoint;
  ...
  private void Form1_MouseMove(object sender, MouseEventArgs e) {
    // prüfen, ob die linke Maustaste gedrückt ist
    if (!mouseFlag)
      return;
    // aus den Mauskoordinaten ein Point-Objekt erzeugen
    Point newPoint = new Point(e.X, e.Y);
    // Referenz auf den Grafikkontext der Form besorgen
    Graphics grfx = this.CreateGraphics();
    // Linie zeichnen
    grfx.DrawLine(new Pen(Brushes.Yellow), lastPoint, newPoint);
    lastPoint = newPoint;
    grfx.Dispose();   
  }
  private void Form1_MouseDown(object sender, MouseEventArgs e) { 
    // prüfen, ob die linke Maustaste gedrückt ist
    if (e.Button != MouseButtons.Left)
      return;
    lastPoint = new Point(e.X, e.Y);
    mouseFlag = true;
  }
  private void Form1_MouseUp(object sender, MouseEventArgs e) {
    mouseFlag = false;
  }
}

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 22.32   Eine sehr ansehnliche Handzeichnung mit der Maus

Weil das Programm nur auf die linke Maustaste reagieren soll, wird der Zustand der gedrückten linken Maustaste durch die boolesche Variable mouseFlag beschrieben. Ist die Variable true, ist die linke Taste gedrückt. Gesetzt wird mouseFlag im MouseDown-Ereignis ebenso wie die Variable lastPoint, die im MouseDown-Ereignis den Startpunkt der Maus auf der Form festhält. lastPoint wird als Startpunkt der Zeichnung interpretiert, die im sich anschließenden MouseMove-Ereignis gezeichnet wird.

Solange die Maustaste gedrückt bleibt, werden unablässig MouseMove-Ereignisse ausgelöst. Weil bei jeder Auslösung eine neue Linie gezeichnet wird, dient lastPoint auch der kontinuierlichen Aneinanderreihung der Linien, die sich als geschlossenes grafisches Element präsentieren.

Das MouseMove-Ereignis stellt die Referenz auf den Grafikkontext des Formulars nicht per Definition bereit. Diesen besorgen wir uns daher mit der Anweisung:


Graphics grfx = this.CreateGraphics();

Im MouseUp-Ereignis wird mouseFlag auf den neutralen false-Zustand zurückgesetzt.

Führen Sie das Programm aus, werden Sie allerdings noch einen nicht akzeptablen Nebeneffekt erkennen: Die Grafik wird nicht restauriert, wenn sich ein anderes Fenster über die Form schiebt und danach das Grafikfenster wieder freigibt. Zur Lösung des Problems würde es sich anbieten, alle Punkte der Grafik in einem Array zu speichern. Der Aufwand der Codeimplementierung steigt aber auch mit der Komplexität der Grafik deutlich an. Unter Umständen können sogar Performanceaspekte gegen diesen Lösungsansatz sprechen, wenn sich das Bild aus zu vielen Linien zusammensetzt.

Beispielprogramm »MiniDraw«

Besser ist es, in solchen Fällen die Grafik in ein Bitmap zu kopieren. Das erfordert nur wenig Programmieraufwand, und die Performance ist unabhängig von der Komplexität der Grafik. Im folgenden Beispiel MiniDraw wird dieses Verfahren gezeigt. Darüber hinaus kann die Freihandzeichnung gespeichert und zu einem späteren Zeitpunkt auch wieder geladen werden, um die begonnene Zeichnung weiter zu bearbeiten.


// -------------------------------------------------------------
// Beispiel: ...\Kapitel 22\MiniDraw
//--------------------------------------------------------------
public partial class Form1 : Form {
  private Bitmap bmp;
  private Graphics graphBMP;
  private Point lastPoint;
  public Form1() {
    InitializeComponent();
    bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
    graphBMP = Graphics.FromImage(bmp);
  }
  private void Form1_Paint(object sender, PaintEventArgs e) {
    e.Graphics.DrawImage(bmp,0,0);
  }
  private void Form1_MouseMove(object sender, MouseEventArgs e) {
    Point newPoint = new Point(e.X, e.Y);
    // prüfen, ob die rechte Maustaste gedrückt ist
    if (e.Button == MouseButtons.Right) {
      newPoint = new Point(e.X, e.Y);
      // Pixel löschen
      graphBMP.DrawLine(new Pen(this.BackColor, 10), lastPoint, newPoint);
      lastPoint = newPoint;
    }
    // wenn linke Maustaste gedrückt ist, Pixel zeichnen
    else if(e.Button == MouseButtons.Left) {
      graphBMP.DrawLine(new Pen(Brushes.Yellow,5), lastPoint, newPoint);
      lastPoint = newPoint;
    }
    // der Aufruf von Invalidate führt zu einem Flackern der Anzeige
    Graphics g = this.CreateGraphics();
    g.DrawImage(bmp,0,0);
    g.Dispose();
  }
  private void Form1_MouseDown(object sender, MouseEventArgs e) {
    lastPoint = new Point(e.X, e.Y);
  }
  private void mnuSave_Click(object sender, EventArgs e) {
    DialogResult dr = saveFileDialog1.ShowDialog();
    if(dr == DialogResult.OK)
      // Bitmap speichern
      bmp.Save(saveFileDialog1.FileName,ImageFormat.Bmp);
  }
  private void mnuOpen_Click(object sender, EventArgs e) {
    // Verarbeitung des MouseMove-Ereignisses abschalten
    this.MouseMove -= new MouseEventHandler(Form1_MouseMove);
    DialogResult dr = openFileDialog1.ShowDialog();
    if(dr == DialogResult.OK) {
      // Stream erzeugen
      FileStream fs = new FileStream(openFileDialog1.FileName,FileMode.Open);
      bmp = new Bitmap(fs);
      // Stream schließen
      fs.Close();
      graphBMP = Graphics.FromImage(bmp);
      this.Invalidate();
    }
    // Nachrichtenwarteschlange leeren
    Application.DoEvents();
    // Verarbeitung des MouseMove-Ereignisses einschalten
    this.MouseMove += new MouseEventHandler(Form1_MouseMove);
  }
}

Auf Klassenebene sind die drei Variablen bmp, graphBMP und lastPoint deklariert. bmp ist die Bitmap, in der die Zeichenoperationen ihr Ziel finden und es mit der Methode DrawImage dem Grafikkontext der Form übergeben wird. graphBMP speichert den Grafikkontext auf die Bitmap, und in lastPoint sind die Koordinaten enthalten, die der letzten registrierten Mauszeigerposition entsprechen.

Wird zur Laufzeit die Maus bewegt, während die linke Maustaste gedrückt ist, wird eine Linie entsprechend der x- und y-Koordinate des Mauszeigers gezeichnet. Analog wird mit der rechten Maustaste die Linie gelöscht, indem die Hintergrundfarbe der gezeichneten Linie der Hintergrundfarbe der Form gesetzt wird.

Das Zeichnen erfolgt im Ereignis MouseMove mit der Methode DrawLine. Gezeichnet wird dabei in den Grafikkontext der Bitmap:


graphBMP.DrawLine(...);

Damit die neue Linie auch in der Form sichtbar wird, muss man die Bitmap dem Grafikkontext der Form übergeben:


Graphics g = this.CreateGraphics();
g.DrawImage(bmp,0,0);

An dieser Stelle wäre man vielleicht dazu geneigt, mit Invalidate das Paint-Ereignis der Form explizit auszulösen, das denselben Code enthält. Allerdings tritt dabei ein Flackern auf, das mit den beiden Anweisungen vermieden wird.

Das Öffnen und Speichern einer Bitmap erfolgt über ein Menü. In beiden Fällen wird dazu ein Standarddialog zur Auswahl bzw. Festlegung der BMP-Datei eingesetzt. Während das Speichern keine Probleme verursacht, ist dem Öffnen einer Bitmap-Datei noch einmal die ganze Aufmerksamkeit zu widmen. Das Problem ist, dass eine Bitmap zum Speichern die Methode Save anbietet, aber dass es dazu keine Entsprechung zum Speichern gibt. Stattdessen müssen wir einen Konstruktor der Klasse Bitmap aufrufen.

Wenn man sich die Liste der Konstruktoren ansieht, ist es im ersten Augenblick verlockend, auf die Überladung zuzugreifen, der eine Zeichenfolge mit der Pfadangabe übergeben wird.


public Bitmap(string filename);

Dieser Konstruktor birgt aber eine Gefahr in sich, die nicht sofort zu erkennen ist. Er erzeugt zwar eine Bitmap anhand der übergebenen Zeichenfolge, aber die Datei, die dazu geöffnet werden muss, wird nicht geschlossen. Die Folge ist, dass die Bitmap anschließend zwar bearbeitet werden kann, aber eine Speicherung unter demselben Dateinamen im gleichen Pfad nicht möglich ist und einen Laufzeitfehler verursacht.

Die Lösung führt über den Konstruktor, dem ein Stream-Objekt übergeben wird. Der Stream, der explizit erzeugt wird, leitet die Daten der eingelesenen Datei an den Bitmap-Konstruktor weiter. Danach kann der Stream ordentlich geschlossen werden.


FileStream fs = new FileStream(...);
bmp = new Bitmap(fs);
fs.Close();

Das Öffnen einer Datei kann durch einen Doppelklick im Standarddialog erfolgen. Es zeigt sich, dass dann durch den Aufruf des MouseMove-Ereignishandlers eine Linie gezeichnet wird, die nicht zu der in der Datei gespeicherten Darstellung gehört. Aus diesem Grund wird im Click-Ereignis des Menüs »Öffnen« zuerst der MouseMove-Handler deregistriert und nach Beendigung aller Operationen neu registriert. Vor der Neuregistrierung muss darüber hinaus mit Application.DoEvents die Nachrichtenschleife geleert werden.

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de